home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1998 January: Mac OS SDK / Dev.CD Jan 98 SDK1.toast / Development Kits (Disc 1) / Macintosh Drag and Drop / Demo Applications / FinderDrag / MoreFiles / Search.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-06-09  |  29.8 KB  |  1,031 lines  |  [TEXT/KAHL]

  1. /*
  2. **    Apple Macintosh Developer Technical Support
  3. **
  4. **    IndexedSearch and the PBCatSearch compatibility function.
  5. **
  6. **    by Jim Luther, Apple Developer Technical Support
  7. **
  8. **    File:        Search.c
  9. **
  10. **    Copyright © 1992-1994 Apple Computer, Inc.
  11. **    All rights reserved.
  12. **
  13. **    You may incorporate this sample code into your applications without
  14. **    restriction, though the sample code has been provided "AS IS" and the
  15. **    responsibility for its operation is 100% yours.  However, what you are
  16. **    not permitted to do is to redistribute the source as "DSC Sample Code"
  17. **    after having made changes. If you're going to re-distribute the source,
  18. **    we require that you make it clear in the source that the code was
  19. **    descended from Apple Sample Code, but that you've made changes.
  20. */
  21.  
  22. #ifndef __SEARCH__
  23. #include "Search.h"
  24. #endif
  25.  
  26.  
  27. /*****************************************************************************/
  28.  
  29. enum
  30. {
  31.     /* Number of LevelRecs to add each time the searchStack is grown */
  32.     /* 20 levels is probably more than reasonable for most volumes. */
  33.     /* If more are needed, they are allocated 20 levels at a time. */
  34.     kAdditionalLevelRecs = 20
  35. };
  36.  
  37. /*****************************************************************************/
  38.  
  39. /*
  40. **    LevelRecs are used to store the directory ID and index whenever
  41. **    IndexedSearch needs to either scan a sub-directory, or return control
  42. **    to the caller because the call has timed out or the number of
  43. **    matches requested has been found. LevelRecs are stored in an array
  44. **    used as a stack.
  45. */
  46. struct    LevelRec
  47. {
  48.     long    dirModDate;    /* for detecting most (but not all) catalog changes */
  49.     long    dirID;
  50.     short    index;
  51. };
  52. typedef struct LevelRec LevelRec;
  53. typedef LevelRec *LevelRecPtr, **LevelRecHandle;
  54.  
  55.  
  56. /*
  57. **    SearchPositionRec is my version of a CatPositionRec. It holds the
  58. **    information I need to resuming searching.
  59. */
  60. #if defined(powerc) || defined (__powerc)
  61. #pragma options align=mac68k
  62. #endif
  63. struct SearchPositionRec
  64. {
  65.     long            initialize;        /* Goofy checksum of volume information used to make */
  66.                                     /* sure we're resuming a search on the same volume. */
  67.     unsigned short    stackDepth;        /* Current depth on searchStack. */
  68.     short            priv[11];        /* For future use... */
  69. };
  70. #if defined(powerc) || defined(__powerc)
  71. #pragma options align=reset
  72. #endif
  73. typedef struct SearchPositionRec SearchPositionRec;
  74. typedef SearchPositionRec *SearchPositionRecPtr;
  75.  
  76.  
  77. /*
  78. **    ExtendedTMTask is a TMTask record extended to hold the timer flag.
  79. */
  80. #if defined(powerc) || defined (__powerc)
  81. #pragma options align=mac68k
  82. #endif
  83. struct ExtendedTMTask
  84. {
  85.     TMTask            theTask;
  86.     Boolean            stopSearch;        /* the Time Mgr task will set stopSearch to */
  87.                                     /* true when the timer expires */
  88. };
  89. #if defined(powerc) || defined(__powerc)
  90. #pragma options align=reset
  91. #endif
  92. typedef struct ExtendedTMTask ExtendedTMTask;
  93. typedef ExtendedTMTask *ExtendedTMTaskPtr;
  94.  
  95. /*****************************************************************************/
  96.  
  97. static    OSErr    CheckVol(StringPtr pathname,
  98.                          short vRefNum,
  99.                          short *realVRefNum,
  100.                          long *volID);
  101.  
  102. static    OSErr    CheckStack(unsigned short stackDepth,
  103.                            LevelRecHandle searchStack,
  104.                            Size *searchStackSize);
  105.  
  106. static    OSErr    VerifyUserPB(CSParamPtr userPB,
  107.                              Boolean *includeFiles,
  108.                              Boolean *includeDirs,
  109.                              Boolean *includeNames);
  110.  
  111. static    Boolean    IsSubString(StringPtr aStringPtr,
  112.                             StringPtr subStringPtr);
  113.  
  114. static    Boolean    CompareMasked(const long *data1,
  115.                               const long *data2,
  116.                               const long *mask,
  117.                               short longsToCompare);
  118.  
  119. static    void    CheckForMatches(CInfoPBPtr cPB,
  120.                                 CSParamPtr userPB,
  121.                                 const Str63 matchName,
  122.                                 Boolean includeFiles,
  123.                                 Boolean includeDirs);
  124.  
  125. #if USESROUTINEDESCRIPTORS
  126.  
  127. static    pascal    void    TimeOutTask(TMTaskPtr tmTaskPtr);
  128.  
  129. #else
  130.  
  131. static    pascal    TMTaskPtr    GetTMTaskPtr(void);
  132.  
  133. static    void    TimeOutTask(void);
  134.  
  135. #endif
  136.  
  137. static    long    GetDirModDate(short vRefNum,
  138.                               long dirID);
  139.  
  140. /*****************************************************************************/
  141.  
  142. /*
  143. **    CheckVol gets the volume's real vRefNum and builds a volID. The volID
  144. **    is used to help insure that calls to resume searching with IndexedSearch
  145. **    are to the same volume as the last call to IndexedSearch.
  146. */
  147. static    OSErr    CheckVol(StringPtr pathname,
  148.                          short vRefNum,
  149.                          short *realVRefNum,
  150.                          long *volID)
  151. {
  152.     HParamBlockRec pb;
  153.     Str255 tempPathname;
  154.     OSErr error;
  155.  
  156.     pb.volumeParam.ioVRefNum = vRefNum;
  157.     if ( pathname == NULL )
  158.     {
  159.         pb.volumeParam.ioNamePtr = NULL;
  160.         pb.volumeParam.ioVolIndex = 0;        /* use ioVRefNum only */
  161.     }
  162.     else
  163.     {
  164.         BlockMoveData(pathname, tempPathname, pathname[0] + 1);    /* make a copy of the string and */
  165.         pb.volumeParam.ioNamePtr = (StringPtr)tempPathname;    /* use the copy so original isn't trashed */
  166.         pb.volumeParam.ioVolIndex = -1;    /* use ioNamePtr/ioVRefNum combination */
  167.     }
  168.     error = PBHGetVInfoSync(&pb);
  169.     if ( error == noErr )
  170.     {
  171.         /* Return the real vRefNum */
  172.         *realVRefNum = pb.volumeParam.ioVRefNum;
  173.  
  174.         /* Add together a bunch of things that aren't supposed to change on */
  175.         /* a mounted volume that's being searched and that should come up with */
  176.         /* a fairly unique number */
  177.         *volID = pb.volumeParam.ioVCrDate +
  178.                  pb.volumeParam.ioVRefNum +
  179.                  pb.volumeParam.ioVNmAlBlks +
  180.                  pb.volumeParam.ioVAlBlkSiz +
  181.                  pb.volumeParam.ioVFSID;
  182.     }
  183.     return ( error );
  184. }
  185.  
  186. /*****************************************************************************/
  187.  
  188. /*
  189. **    CheckStack checks the size of the search stack (array) to see if there's
  190. **    room to push another LevelRec. If not, CheckStack grows the stack by
  191. **    another kAdditionalLevelRecs elements.
  192. */
  193. static    OSErr    CheckStack(unsigned short stackDepth,
  194.                            LevelRecHandle searchStack,
  195.                            Size *searchStackSize)
  196. {
  197.     OSErr    result;
  198.     
  199.     if ( (*searchStackSize / sizeof(LevelRec)) == (stackDepth + 1) )
  200.     {
  201.         /* Time to grow stack */
  202.         SetHandleSize((Handle)searchStack, *searchStackSize + (kAdditionalLevelRecs * sizeof(LevelRec)));
  203.         result = MemError();    /* should be noErr */
  204.         *searchStackSize = GetHandleSize((Handle)searchStack);
  205.     }
  206.     else
  207.     {
  208.         result = noErr;
  209.     }
  210.     
  211.     return ( result );
  212. }
  213.  
  214. /*****************************************************************************/
  215.  
  216. /*
  217. **    VerifyUserPB makes sure the parameter block passed to IndexedSearch has
  218. **    valid parameters. By making this check once, we don't have to worry about
  219. **    things like NULL pointers, strings being too long, etc.
  220. **    VerifyUserPB also determines if the search includes files and/or
  221. **    directories, and determines if a full or partial name search was requested.
  222. */
  223. static    OSErr    VerifyUserPB(CSParamPtr userPB,
  224.                              Boolean *includeFiles,
  225.                              Boolean *includeDirs,
  226.                              Boolean *includeNames)
  227. {
  228.     CInfoPBPtr    searchInfo1;
  229.     CInfoPBPtr    searchInfo2;
  230.     
  231.     searchInfo1 = userPB->ioSearchInfo1;
  232.     searchInfo2 = userPB->ioSearchInfo2;
  233.     
  234.     /* ioMatchPtr cannot be NULL */
  235.     if ( userPB->ioMatchPtr == NULL )
  236.         goto ParamErrExit;
  237.     
  238.     /* ioSearchInfo1 cannot be NULL */
  239.     if ( searchInfo1 == NULL )
  240.         goto ParamErrExit;
  241.     
  242.     /* If any bits except partialName, fullName, or negate are set, then */
  243.     /* ioSearchInfo2 cannot be NULL because information in ioSearchInfo2 is required  */
  244.     if ( ((userPB->ioSearchBits & ~(fsSBPartialName | fsSBFullName | fsSBNegate)) != 0) &&
  245.          ( searchInfo2 == NULL ))
  246.         goto ParamErrExit;
  247.     
  248.     *includeFiles = false;
  249.     *includeDirs = false;
  250.     *includeNames = false;
  251.     
  252.     if ( (userPB->ioSearchBits & (fsSBPartialName | fsSBFullName)) != 0 )
  253.     {
  254.         /* If any kind of name matching is requested, then ioNamePtr in */
  255.         /* ioSearchInfo1 cannot be NULL or a zero-length string */
  256.         if ( (searchInfo1->hFileInfo.ioNamePtr == NULL) ||
  257.              (searchInfo1->hFileInfo.ioNamePtr[0] == 0) ||
  258.              (searchInfo1->hFileInfo.ioNamePtr[0] > (sizeof(Str63) - 1)) )
  259.             goto ParamErrExit;
  260.         
  261.         *includeNames = true;
  262.     }
  263.     
  264.     if ( (userPB->ioSearchBits & fsSBFlAttrib) != 0 )
  265.     {
  266.         /* The only attributes you can search on are the directory flag */
  267.         /* and the locked flag. */
  268.         if ( (searchInfo2->hFileInfo.ioFlAttrib & ~(ioDirMask | 0x01)) != 0 )
  269.             goto ParamErrExit;
  270.         
  271.         /* interested in the directory bit? */
  272.         if ( (searchInfo2->hFileInfo.ioFlAttrib & ioDirMask) != 0 )
  273.         {
  274.             /* yes, so do they want just directories or just files? */
  275.             if ( (searchInfo1->hFileInfo.ioFlAttrib & ioDirMask) != 0 )
  276.                 *includeDirs = true;
  277.             else
  278.                 *includeFiles = true;
  279.         }
  280.         else
  281.         {
  282.             /* no interest in directory bit - get both files and directories */
  283.             *includeDirs = true;
  284.             *includeFiles = true;
  285.         }
  286.     }
  287.     else
  288.     {
  289.         /* no attribute checking - get both files and directories */
  290.         *includeDirs = true;
  291.         *includeFiles = true;
  292.     }
  293.     
  294.     /* If directories are included in the search, */
  295.     /* then the locked attribute cannot be requested. */
  296.     if ( *includeDirs &&
  297.          ((userPB->ioSearchBits & fsSBFlAttrib) != 0) &&
  298.          ((searchInfo2->hFileInfo.ioFlAttrib & 0x01) != 0) )
  299.         goto ParamErrExit;
  300.     
  301.     /* If files are included in the search, then there cannot be */
  302.     /* a search on the number of files. */
  303.     if ( *includeFiles &&
  304.          ((userPB->ioSearchBits & fsSBDrNmFls) != 0) )
  305.         goto ParamErrExit;
  306.     
  307.     /* If directories are included in the search, then there cannot */
  308.     /* be a search on file lengths. */
  309.     if ( *includeDirs &&
  310.          ((userPB->ioSearchBits & (fsSBFlLgLen | fsSBFlPyLen | fsSBFlRLgLen | fsSBFlRPyLen)) != 0) )
  311.         goto ParamErrExit;
  312.     
  313.     return ( noErr );
  314.          
  315. ParamErrExit:
  316.     return ( paramErr );
  317. }
  318.  
  319. /*****************************************************************************/
  320.  
  321. /*
  322. **    IsSubString checks to see if a string is a substring of another string.
  323. **    Both input strings have already been converted to all uppercase using
  324. **    UprString (the same non-international call the File Manager uses).
  325. */
  326. static    Boolean    IsSubString(StringPtr aStringPtr,
  327.                             StringPtr subStringPtr)
  328. {
  329.     short    strLength;        /* length of string */
  330.     short    subStrLength;    /* length of subString */
  331.     Boolean    found;            /* result of test */
  332.     short    index;            /* current index into string */
  333.     
  334.     found = false;
  335.     strLength = aStringPtr[0];
  336.     subStrLength = subStringPtr[0];
  337.         
  338.     if ( subStrLength <= strLength)
  339.     {
  340.         register short    count;            /* search counter */
  341.         register short    strIndex;        /* running index into string */
  342.         register short    subStrIndex;    /* running index into subString */
  343.         
  344.         /* start looking at first character */
  345.         index = 1;
  346.         
  347.         /* continue looking until remaining string is shorter than substring */
  348.         count = strLength - subStrLength + 1;
  349.         
  350.         do
  351.         {
  352.             strIndex = index;    /* start string index at index */
  353.             subStrIndex = 1;    /* start subString index at 1 */
  354.             
  355.             while ( !found && (aStringPtr[strIndex] == subStringPtr[subStrIndex]) )
  356.             {
  357.                 if ( subStrIndex == subStrLength )
  358.                 {
  359.                     /* all characters in subString were found */
  360.                     found = true;
  361.                 }
  362.                 else
  363.                 {
  364.                     /* check next character of substring against next character of string */
  365.                     ++subStrIndex;
  366.                     ++strIndex;
  367.                 }
  368.             }
  369.             
  370.             if ( !found )
  371.             {
  372.                 /* start substring search again at next string character */
  373.                 ++index;
  374.                 --count;
  375.             }
  376.         } while ( count != 0 && (!found) );
  377.     }
  378.     
  379.     return ( found );
  380. }
  381.  
  382. /*****************************************************************************/
  383.  
  384. /*
  385. **    CompareMasked does a bitwise comparison with mask on 1 or more longs.
  386. **    data1 and data2 are first exclusive-ORed together resulting with bits set
  387. **    where they are different. That value is then ANDed with the mask resulting
  388. **    with bits set if the test fails. true is returned if the tests pass.
  389. */
  390. static    Boolean    CompareMasked(const long *data1,
  391.                               const long *data2,
  392.                               const long *mask,
  393.                               short longsToCompare)
  394. {
  395.     Boolean    result = true;
  396.     
  397.     while ( (longsToCompare != 0) && (result == true) )
  398.     {
  399.         /* (*data1 ^ *data2) = bits that are different, so... */
  400.         /* ((*data1 ^ *data2) & *mask) = bits that are different that we're interested in */
  401.         
  402.         if ( ((*data1 ^ *data2) & *mask) != 0 )
  403.             result = false;
  404.         
  405.         ++data1;
  406.         ++data2;
  407.         ++mask;
  408.         --longsToCompare;
  409.     }
  410.     
  411.     return ( result );
  412. }
  413.  
  414. /*****************************************************************************/
  415.  
  416. /*
  417. **    Check for matches compares the search criteria in userPB to the file
  418. **    system object in cPB. If there's a match, then the information in cPB is
  419. **    is added to the match array and the actual match count is incremented.
  420. */
  421. static    void    CheckForMatches(CInfoPBPtr cPB,
  422.                                 CSParamPtr userPB,
  423.                                 const Str63 matchName,
  424.                                 Boolean includeFiles,
  425.                                 Boolean includeDirs)
  426. {
  427.     long        searchBits;
  428.     CInfoPBPtr    searchInfo1;
  429.     CInfoPBPtr    searchInfo2;
  430.     Str63        itemName;        /* copy of object's name for partial name matching */
  431.     Boolean        foundMatch;
  432.     
  433.     foundMatch = false;            /* default to no match */
  434.     
  435.     searchBits = userPB->ioSearchBits;
  436.     searchInfo1 = userPB->ioSearchInfo1;
  437.     searchInfo2 = userPB->ioSearchInfo2;
  438.     
  439.     /* Into the if statements that go on forever... */
  440.     
  441.     if ( (cPB->hFileInfo.ioFlAttrib & ioDirMask) == 0 )
  442.     {
  443.         if (!includeFiles)
  444.             goto Failed;
  445.     }
  446.     else
  447.     {
  448.         if (!includeDirs)
  449.             goto Failed;
  450.     }
  451.     
  452.     if ( (searchBits & fsSBPartialName) != 0 )
  453.     {
  454.         if ( (cPB->hFileInfo.ioNamePtr[0] > 0) &&
  455.              (cPB->hFileInfo.ioNamePtr[0] <= (sizeof(Str63) - 1)) )
  456.         {
  457.             /* Make uppercase copy of object name */
  458.             BlockMoveData(cPB->hFileInfo.ioNamePtr,
  459.                             itemName,
  460.                             cPB->hFileInfo.ioNamePtr[0] + 1);
  461.             /* Use the same non-international call the File Manager uses */
  462.             UprString(itemName, true);
  463.         }
  464.         else
  465.         {
  466.             goto Failed;
  467.         }
  468.         
  469.         {
  470.             if ( !IsSubString(itemName, (StringPtr)matchName) )
  471.             {
  472.                 goto Failed;
  473.             }
  474.             else if ( searchBits == fsSBPartialName )
  475.             {
  476.                 /* optimize for name matching only since it is most common way to search */
  477.                 goto Hit;
  478.             }
  479.         }
  480.     }
  481.     
  482.     if ( (searchBits & fsSBFullName) != 0 )
  483.     {
  484.         /* Use the same non-international call the File Manager uses */
  485.         if ( !EqualString(cPB->hFileInfo.ioNamePtr, matchName, false, true) )
  486.         {
  487.             goto Failed;
  488.         }
  489.         else if ( searchBits == fsSBFullName )
  490.         {
  491.             /* optimize for name matching only since it is most common way to search */
  492.             goto Hit;
  493.         }
  494.     }
  495.     
  496.     if ( (searchBits & fsSBFlParID) != 0 )
  497.     {
  498.         if ( ((unsigned long)(cPB->hFileInfo.ioFlParID) < (unsigned long)(searchInfo1->hFileInfo.ioFlParID)) ||
  499.              ((unsigned long)(cPB->hFileInfo.ioFlParID) > (unsigned long)(searchInfo2->hFileInfo.ioFlParID)) )
  500.         {
  501.             goto Failed;
  502.         }
  503.     }
  504.     
  505.     if ( (searchBits & fsSBFlAttrib) != 0 )
  506.     {
  507.         if ( ((cPB->hFileInfo.ioFlAttrib ^ searchInfo1->hFileInfo.ioFlAttrib) &
  508.               searchInfo2->hFileInfo.ioFlAttrib) != 0 )
  509.         {
  510.             goto Failed;
  511.         }
  512.     }
  513.     
  514.     if ( (searchBits & fsSBDrNmFls) != 0 )
  515.     {
  516.         if ( ((unsigned long)(cPB->dirInfo.ioDrNmFls) < (unsigned long)(searchInfo1->dirInfo.ioDrNmFls)) ||
  517.              ((unsigned long)(cPB->dirInfo.ioDrNmFls) > (unsigned long)(searchInfo2->dirInfo.ioDrNmFls)) )
  518.         {
  519.             goto Failed;
  520.         }
  521.     }
  522.  
  523.     if ( (searchBits & fsSBFlFndrInfo) != 0 )    /* fsSBFlFndrInfo is same as fsSBDrUsrWds */
  524.     {
  525.         if ( !CompareMasked((long *)&(cPB->hFileInfo.ioFlFndrInfo),
  526.                             (long *)&(searchInfo1->hFileInfo.ioFlFndrInfo),
  527.                             (long *)&(searchInfo2->hFileInfo.ioFlFndrInfo),
  528.                             sizeof(FInfo) / sizeof(long)) )
  529.         {
  530.             goto Failed;
  531.         }
  532.     }
  533.     
  534.     if ( (searchBits & fsSBFlXFndrInfo) != 0 )    /* fsSBFlXFndrInfo is same as fsSBDrFndrInfo */
  535.     {
  536.         if ( !CompareMasked((long *)&(cPB->hFileInfo.ioFlXFndrInfo),
  537.                             (long *)&(searchInfo1->hFileInfo.ioFlXFndrInfo),
  538.                             (long *)&(searchInfo2->hFileInfo.ioFlXFndrInfo),
  539.                             sizeof(FXInfo) / sizeof(long)) )
  540.         {
  541.             goto Failed;
  542.         }
  543.     }
  544.     
  545.     if ( (searchBits & fsSBFlLgLen) != 0 )
  546.     {
  547.         if ( ((unsigned long)(cPB->hFileInfo.ioFlLgLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlLgLen)) ||
  548.              ((unsigned long)(cPB->hFileInfo.ioFlLgLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlLgLen)) )
  549.         {
  550.             goto Failed;
  551.         }
  552.     }
  553.  
  554.     if ( (searchBits & fsSBFlPyLen) != 0 )
  555.     {
  556.         if ( ((unsigned long)(cPB->hFileInfo.ioFlPyLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlPyLen)) ||
  557.              ((unsigned long)(cPB->hFileInfo.ioFlPyLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlPyLen)) )
  558.         {
  559.             goto Failed;
  560.         }
  561.     }
  562.  
  563.     if ( (searchBits & fsSBFlRLgLen) != 0 )
  564.     {
  565.         if ( ((unsigned long)(cPB->hFileInfo.ioFlRLgLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlRLgLen)) ||
  566.              ((unsigned long)(cPB->hFileInfo.ioFlRLgLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlRLgLen)) )
  567.         {
  568.             goto Failed;
  569.         }
  570.     }
  571.  
  572.     if ( (searchBits & fsSBFlRPyLen) != 0 )
  573.     {
  574.         if ( ((unsigned long)(cPB->hFileInfo.ioFlRPyLen) < (unsigned long)(searchInfo1->hFileInfo.ioFlRPyLen)) ||
  575.              ((unsigned long)(cPB->hFileInfo.ioFlRPyLen) > (unsigned long)(searchInfo2->hFileInfo.ioFlRPyLen)) )
  576.         {
  577.             goto Failed;
  578.         }
  579.     }
  580.  
  581.     if ( (searchBits & fsSBFlCrDat) != 0 )    /* fsSBFlCrDat is same as fsSBDrCrDat */
  582.     {
  583.         if ( ((unsigned long)(cPB->hFileInfo.ioFlCrDat) < (unsigned long)(searchInfo1->hFileInfo.ioFlCrDat)) ||
  584.              ((unsigned long)(cPB->hFileInfo.ioFlCrDat) > (unsigned long)(searchInfo2->hFileInfo.ioFlCrDat)) )
  585.         {
  586.             goto Failed;
  587.         }
  588.     }
  589.  
  590.     if ( (searchBits & fsSBFlMdDat) != 0 )    /* fsSBFlMdDat is same as fsSBDrMdDat */
  591.     {
  592.         if ( ((unsigned long)(cPB->hFileInfo.ioFlMdDat) < (unsigned long)(searchInfo1->hFileInfo.ioFlMdDat)) ||
  593.              ((unsigned long)(cPB->hFileInfo.ioFlMdDat) > (unsigned long)(searchInfo2->hFileInfo.ioFlMdDat)) )
  594.         {
  595.             goto Failed;
  596.         }
  597.     }
  598.  
  599.     if ( (searchBits & fsSBFlBkDat) != 0 )    /* fsSBFlBkDat is same as fsSBDrBkDat */
  600.     {
  601.         if ( ((unsigned long)(cPB->hFileInfo.ioFlBkDat) < (unsigned long)(searchInfo1->hFileInfo.ioFlBkDat)) ||
  602.              ((unsigned long)(cPB->hFileInfo.ioFlBkDat) > (unsigned long)(searchInfo2->hFileInfo.ioFlBkDat)) )
  603.         {
  604.             goto Failed;
  605.         }
  606.     }
  607.  
  608.     /* Hey, we passed all of the tests! */
  609.     
  610. Hit:
  611.     foundMatch = true;
  612.  
  613. /* foundMatch is false if code jumps to Failed */
  614. Failed:
  615.     /* Do we reverse our findings? */
  616.     if ( (searchBits & fsSBNegate) != 0 )
  617.         foundMatch = !foundMatch;    /* matches are not, not matches are */
  618.     
  619.     if ( foundMatch )
  620.     {
  621.  
  622.         /* Move the match into the match buffer */
  623.         userPB->ioMatchPtr[userPB->ioActMatchCount].vRefNum = cPB->hFileInfo.ioVRefNum;
  624.         userPB->ioMatchPtr[userPB->ioActMatchCount].parID = cPB->hFileInfo.ioFlParID;
  625.         if ( cPB->hFileInfo.ioNamePtr[0] > 63 )
  626.             cPB->hFileInfo.ioNamePtr[0] = 63;
  627.         BlockMoveData(cPB->hFileInfo.ioNamePtr,
  628.                       userPB->ioMatchPtr[userPB->ioActMatchCount].name,
  629.                       cPB->hFileInfo.ioNamePtr[0] + 1);
  630.         
  631.         /* increment the actual count */
  632.         ++(userPB->ioActMatchCount);
  633.     }
  634. }
  635.  
  636. /*****************************************************************************/
  637.  
  638. /*
  639. **    TimeOutTask is executed when the timer goes off. It simply sets the
  640. **    stopSearch field to true. After each object is found and possibly added
  641. **    to the matches buffer, stopSearch is checked to see if the search should
  642. **    continue.
  643. */
  644.  
  645. #if USESROUTINEDESCRIPTORS
  646.  
  647. static    pascal    void    TimeOutTask(TMTaskPtr tmTaskPtr)
  648. {
  649.     ((ExtendedTMTaskPtr)tmTaskPtr)->stopSearch = true;
  650. }
  651.  
  652. #else
  653.  
  654. static    pascal    TMTaskPtr    GetTMTaskPtr(void)
  655.     ONEWORDINLINE(0x2e89);    /* MOVE.L A1,(SP) */
  656.     
  657. static    void    TimeOutTask(void)
  658. {
  659.     ((ExtendedTMTaskPtr)GetTMTaskPtr())->stopSearch = true;
  660. }
  661.  
  662. #endif
  663.  
  664. /*****************************************************************************/
  665.  
  666. /*
  667. **    GetDirModDate returns the modification date of a directory. If there is
  668. **    an error getting the modification date, -1 is returned to indicate
  669. **    something went wrong.
  670. */
  671. static    long    GetDirModDate(short vRefNum,
  672.                               long dirID)
  673. {
  674.     CInfoPBRec pb;
  675.     OSErr modDate;
  676.  
  677.     pb.dirInfo.ioNamePtr = NULL;
  678.     pb.dirInfo.ioVRefNum = vRefNum;
  679.     pb.dirInfo.ioDrDirID = dirID;
  680.     pb.dirInfo.ioFDirIndex = -1;    /* use ioDrDirID */
  681.     if ( PBGetCatInfoSync(&pb) == noErr )
  682.     {
  683.         modDate = pb.dirInfo.ioDrMdDat;
  684.     }
  685.     else
  686.     {
  687.         modDate = -1;
  688.     }
  689.     
  690.     return ( modDate );
  691. }
  692. /*****************************************************************************/
  693.  
  694. pascal    OSErr    IndexedSearch(CSParamPtr pb,
  695.                               long dirID)
  696. {
  697.     static LevelRecHandle    searchStack = NULL;        /* static handle to LevelRec stack */
  698.     static Size                searchStackSize = 0;    /* size of static handle */
  699.     SearchPositionRecPtr    catPosition;
  700.     long                    modDate;
  701.     short                    index;
  702.     ExtendedTMTask            timerTask;
  703.     OSErr                    result;
  704.     short                    realVRefNum;
  705.     Str63                    itemName;
  706.     CInfoPBRec                cPB;
  707.     long                    tempLong;
  708.     Boolean                    includeFiles;
  709.     Boolean                    includeDirs;
  710.     Boolean                    includeNames;
  711.     Str63                    upperName;
  712.     
  713.     timerTask.stopSearch = false;    /* don't stop yet! */
  714.     
  715.     /* If request has a timeout, install a Time Manager task. */
  716.     if ( pb->ioSearchTime != 0 )
  717.     {
  718.         /* Start timer */
  719.         timerTask.theTask.tmAddr = NewTimerProc(TimeOutTask);
  720.         InsTime((QElemPtr)&(timerTask.theTask));
  721.         PrimeTime((QElemPtr)&(timerTask.theTask), pb->ioSearchTime);
  722.     }
  723.     
  724.     /* Check the parameter block passed for things that we don't want to assume */
  725.     /* are OK later in the code. For example, make sure pointers to data structures */
  726.     /* and buffers are not NULL.  And while we're in there, see if the request */
  727.     /* specified searching for files, directories, or both, and see if the search */
  728.     /* was by full or partial name. */
  729.     result = VerifyUserPB(pb, &includeFiles, &includeDirs, &includeNames);
  730.     if ( result == noErr )
  731.     {
  732.         pb->ioActMatchCount = 0;    /* no matches yet */
  733.     
  734.         if ( includeNames )
  735.         {
  736.             /* The search includes seach by full or partial name. */
  737.             /* Make an upper case copy of the match string to pass to */
  738.             /* CheckForMatches. */
  739.             BlockMoveData(pb->ioSearchInfo1->hFileInfo.ioNamePtr,
  740.                             upperName,
  741.                             pb->ioSearchInfo1->hFileInfo.ioNamePtr[0] + 1);
  742.             /* Use the same non-international call the File Manager uses */
  743.             UprString(upperName, true);
  744.         }
  745.         
  746.         /* Prevent casting to my type throughout code */
  747.         catPosition = (SearchPositionRecPtr)&pb->ioCatPosition;
  748.         
  749.         /* Create searchStack first time called */
  750.         if ( searchStack == NULL )
  751.         {
  752.             searchStack = (LevelRecHandle)NewHandle(kAdditionalLevelRecs * sizeof(LevelRec));
  753.         }
  754.         
  755.         /* Make sure searchStack really exists */
  756.         if ( searchStack != NULL )
  757.         {
  758.             searchStackSize = GetHandleSize((Handle)searchStack);
  759.             
  760.             /* See if the search is a new search or a resumed search. */
  761.             if ( catPosition->initialize == 0 )
  762.             {
  763.                 /* New search. */
  764.                 
  765.                 /* Get the real vRefNum and fill in catPosition->initialize. */ 
  766.                 result = CheckVol(pb->ioNamePtr, pb->ioVRefNum, &realVRefNum, &catPosition->initialize);
  767.                 if ( result == noErr )
  768.                 {
  769.                     /* clear searchStack */
  770.                     catPosition->stackDepth = 0;
  771.                     
  772.                     /* use dirID parameter passed and... */
  773.                     index = -1;    /* start with the passed directory itself! */
  774.                 }
  775.             }
  776.             else
  777.             {
  778.                 /* We're resuming a search. */
  779.     
  780.                 /* Get the real vRefNum and make sure catPosition->initialize is valid. */ 
  781.                 result = CheckVol(pb->ioNamePtr, pb->ioVRefNum, &realVRefNum, &tempLong);
  782.                 if ( result == noErr )
  783.                 {
  784.                     /* Make sure the resumed search is to the same volume! */
  785.                     if ( catPosition->initialize == tempLong )
  786.                     {
  787.                         /* For resume, catPosition->stackDepth > 0 */
  788.                         if ( catPosition->stackDepth > 0 )
  789.                         {
  790.                             /* Position catPosition->stackDepth to access last saved level */
  791.                             --(catPosition->stackDepth);
  792.             
  793.                             /* Get the dirID and index for the next item */
  794.                             dirID = (*searchStack)[catPosition->stackDepth].dirID;
  795.                             index = (*searchStack)[catPosition->stackDepth].index;
  796.                             
  797.                             /* Check the dir's mod date against the saved mode date on our "stack" */
  798.                             modDate = GetDirModDate(realVRefNum, dirID);
  799.                             if ( modDate != (*searchStack)[catPosition->stackDepth].dirModDate )
  800.                                 result = catChangedErr;
  801.                         }
  802.                         else
  803.                         {
  804.                             /* Invalid catPosition record was passed */
  805.                             result = paramErr;
  806.                         }
  807.                     }
  808.                     else
  809.                     {
  810.                         /* The volume is not the same */
  811.                         result = catChangedErr;
  812.                     }
  813.                 }
  814.             }
  815.             
  816.             if ( result == noErr )
  817.             {
  818.                 /* ioNamePtr and ioVRefNum only need to be set up once. */
  819.                 cPB.hFileInfo.ioNamePtr = itemName;
  820.                 cPB.hFileInfo.ioVRefNum = realVRefNum;
  821.                 
  822.                 /*
  823.                 **    Here's the loop that:
  824.                 **        Finds the next item on the volume.
  825.                 **        If noErr, calls the code to check for matches and add matches
  826.                 **            to the match buffer.
  827.                 **        Sets up dirID and index for to find the next item on the volume.
  828.                 **
  829.                 **    The looping ends when:
  830.                 **        a) an unexpected error is returned by PBGetCatInfo. All that
  831.                 **            is expected is noErr and fnfErr (after the last item in a
  832.                 **            directory is found).
  833.                 **        b) the caller specified a timeout and our Time Manager task
  834.                 **            has fired.
  835.                 **        c) the number of matches requested by the caller has been found.
  836.                 **        d) the last item on the volume was found.
  837.                 */
  838.                 do
  839.                 {
  840.                     /* get the next item */
  841.                     cPB.hFileInfo.ioFDirIndex = index;
  842.                     cPB.hFileInfo.ioDirID = dirID;
  843.                     result = PBGetCatInfoSync(&cPB);
  844.                     if ( index != -1 )
  845.                     {
  846.                         if ( result == noErr )
  847.                         {
  848.                             /* We found something */
  849.         
  850.                             CheckForMatches(&cPB, pb, upperName, includeFiles, includeDirs);
  851.                             
  852.                             ++index;
  853.                             if ( (cPB.dirInfo.ioFlAttrib & ioDirMask) != 0 )
  854.                             {
  855.                                 /* It's a directory */
  856.                                 
  857.                                 result = CheckStack(catPosition->stackDepth, searchStack, &searchStackSize);
  858.                                 if ( result == noErr )
  859.                                 {
  860.                                     /* Save the current state on the searchStack */
  861.                                     /* when we come back, this is where we'll start */
  862.                                     (*searchStack)[catPosition->stackDepth].dirID = dirID;
  863.                                     (*searchStack)[catPosition->stackDepth].index = index;
  864.                                     (*searchStack)[catPosition->stackDepth].dirModDate = GetDirModDate(realVRefNum, dirID);
  865.                                     
  866.                                     /* position catPosition->stackDepth for next saved level */
  867.                                     ++(catPosition->stackDepth);
  868.                                     
  869.                                     /* The next item to get is the 1st item in the child directory */
  870.                                     dirID = cPB.dirInfo.ioDrDirID;
  871.                                     index = 1;
  872.                                 }
  873.                             }
  874.                             /* else do nothing for files */
  875.                         }
  876.                         else
  877.                         {
  878.                             /* End of directory found (or we had some error and that */
  879.                             /* means we have to drop out of this directory). */
  880.                             /* Restore last thing put on stack and */
  881.                             /* see if we need to continue or quit. */
  882.                             if ( catPosition->stackDepth > 0 )
  883.                             {
  884.                                 /* position catPosition->stackDepth to access last saved level */
  885.                                 --(catPosition->stackDepth);
  886.                                 
  887.                                 dirID = (*searchStack)[catPosition->stackDepth].dirID;
  888.                                 index = (*searchStack)[catPosition->stackDepth].index;
  889.                                 
  890.                                 /* Check the dir's mod date against the saved mode date on our "stack" */
  891.                                 modDate = GetDirModDate(realVRefNum, dirID);
  892.                                 if ( modDate != (*searchStack)[catPosition->stackDepth].dirModDate )
  893.                                 {
  894.                                     result = catChangedErr;
  895.                                 }
  896.                                 else
  897.                                 {
  898.                                     /* Going back to ancestor directory. */
  899.                                     /* Clear error so we can continue. */
  900.                                     result = noErr;
  901.                                 }
  902.                             }
  903.                             else
  904.                             {
  905.                                 /* We hit the bottom of the stack, so we'll let the */
  906.                                 /* the eofErr drop us out of the loop. */
  907.                                 result = eofErr;
  908.                             }
  909.                         }
  910.                     }
  911.                     else
  912.                     {
  913.                         /* Special case for index == -1; that means that we're starting */
  914.                         /* a new search and so the first item to check is the directory */
  915.                         /* passed to us. */
  916.                         if ( result == noErr )
  917.                         {
  918.                             /* We found something */
  919.         
  920.                             CheckForMatches(&cPB, pb, upperName, includeFiles, includeDirs);
  921.                             
  922.                             /* Now, set the index to 1 and then we're ready to look inside */
  923.                             /* the passed directory. */
  924.                             index = 1;
  925.                         }
  926.                     }
  927.                 } while ( (!timerTask.stopSearch) &&    /* timer hasn't fired */
  928.                           (result == noErr) &&            /* no unexpected errors */
  929.                           (pb->ioReqMatchCount > pb->ioActMatchCount) ); /* we haven't found our limit */
  930.                 
  931.                 /* Did we drop out of the loop because of timeout or */
  932.                 /* ioReqMatchCount was found? */
  933.                 if ( result == noErr )
  934.                 {
  935.                     result = CheckStack(catPosition->stackDepth, searchStack, &searchStackSize);
  936.                     if ( result == noErr )
  937.                     {
  938.                         /* Either there was a timeout or ioReqMatchCount was reached. */
  939.                         /* Save the dirID and index for the next time we're called. */
  940.                         
  941.                         (*searchStack)[catPosition->stackDepth].dirID = dirID;
  942.                         (*searchStack)[catPosition->stackDepth].index = index;
  943.                         (*searchStack)[catPosition->stackDepth].dirModDate = GetDirModDate(realVRefNum, dirID);
  944.                         
  945.                         /* position catPosition->stackDepth for next saved level */
  946.                         
  947.                         ++(catPosition->stackDepth);
  948.                     }
  949.                 }
  950.             }
  951.         }
  952.         else
  953.         {
  954.             /* searchStack Handle could not be allocated */
  955.             result = memFullErr;
  956.         }
  957.     }
  958.     
  959.     if ( pb->ioSearchTime != 0 )
  960.     {
  961.         /* Stop Time Manager task here if it was installed */
  962.         RmvTime((QElemPtr)&(timerTask.theTask));
  963.         DisposeRoutineDescriptor(timerTask.theTask.tmAddr);
  964.     }
  965.     
  966.     return ( result );
  967. }
  968.  
  969. /*****************************************************************************/
  970.  
  971. pascal OSErr PBCatSearchSyncCompat(CSParamPtr paramBlock)
  972. {
  973.     static Boolean            fullExtFSDispatchingtested = false;
  974.     static Boolean            hasFullExtFSDispatching = false;
  975.     OSErr                     result;
  976.     Boolean                    supportsCatSearch;
  977.     long                    response;
  978.     GetVolParmsInfoBuffer    volParmsInfo;
  979.     long                    infoSize;
  980.     
  981.     result = noErr;
  982.  
  983.     /* See if File Manager will pass CatSearch requests to external file systems */
  984.     /* we'll store the results in a static variable so we don't have to call Gestalt */
  985.     /* everytime we're called. */
  986.     if ( !fullExtFSDispatchingtested )
  987.     {
  988.         fullExtFSDispatchingtested = true;
  989.         if ( Gestalt(gestaltFSAttr, &response) == noErr )
  990.         {
  991.             hasFullExtFSDispatching = ((response & (1L << gestaltFullExtFSDispatching)) != 0);
  992.         }
  993.     }
  994.     
  995.     /* CatSearch is a per volume attribute, so we have to check each time we're */
  996.     /* called to see if it is available on the volume specified. */
  997.     supportsCatSearch = false;
  998.     if ( hasFullExtFSDispatching )
  999.     {
  1000.         infoSize = sizeof(GetVolParmsInfoBuffer);
  1001.         result = HGetVolParms(paramBlock->ioNamePtr, paramBlock->ioVRefNum,
  1002.                             &volParmsInfo, &infoSize);
  1003.         if ( result == noErr )
  1004.         {
  1005.             supportsCatSearch = hasCatSearch(volParmsInfo);
  1006.         }
  1007.     }
  1008.     
  1009.     /* noErr or paramErr is OK here. */
  1010.     /* paramErr just means that GetVolParms isn't supported by this volume */
  1011.     if ( (result == noErr) || (result == paramErr) )
  1012.     {
  1013.         if ( supportsCatSearch )
  1014.         {
  1015.             /* Volume supports CatSearch so use it. */
  1016.             /* CatSearch is faster than an indexed search. */
  1017.             result = PBCatSearchSync(paramBlock);
  1018.         }
  1019.         else
  1020.         {
  1021.             /* Volume doesn't support CatSearch so */
  1022.             /* search using IndexedSearch from root directory. */
  1023.             result = IndexedSearch(paramBlock, fsRtDirID);
  1024.         }
  1025.     }
  1026.     
  1027.     return ( result );
  1028. }
  1029.  
  1030. /*****************************************************************************/
  1031.